/*
 * FileInfo.cpp
 *
 *  Created on: 24.06.2011
 *      Author: mario
 */

#include "FileInfo.h"

using namespace std;

FileInfo::FileInfo()  :
    key(""), filename(""), size(0),
    transferred(0), transfer_accepted(false), transfer_paused(false), try_resume_paused_transfer(false),
    time_consumed(0), unpaused_at(0), unacked_pkg(0), finished(false)
{
}


FileInfo::FileInfo(string key, string filename, uint64_t size, UserPtr partner) :
    key(key), filename(filename), size(size),
    transferred(0), partner(partner), transfer_accepted(false), transfer_paused(false), try_resume_paused_transfer(false),
    time_consumed(0), unpaused_at(0), unacked_pkg(0), finished(false)
{
}

// load FileInfo from PropertyTree
FileInfo::FileInfo(ptree &pt, Userdatabase* users) :
        key(pt.get<string>("key")),
        filename(pt.get<string>("filename")),
        path(pt.get<string>("path")),
        time_consumed(pt.get("time_consumed", 0)),
        unpaused_at(0),
        unacked_pkg(0),
        finished(false)
{
    size = pt.get<uint64_t>("size");
    transferred = pt.get<uint64_t>("transferred");
    // TODO Das muss unbedingt schöner gemacht werden, hier ist ja vollkommen unklar ob es ein Infile oder ein Outfile ist,
    // aber es muss unterschiedliches Verhalten sein. ---> Am besten im Zuge wenn Infile- und Outfile-Klassen getrennt werden..
    // ----> Was soll dieser Code überhaupt? War das vielleicht nur Debug? Soll es überhaupt bleiben?
    if (boost::filesystem::is_regular(path))
    {
        uint64_t x = boost::filesystem::file_size(path);
        if ( x == size )
        {
            cout << "File size == SIZE ---> assuming an outfile." << endl;
        }
        else if ( x != transferred)
        {
            cout << "ACHTUNG !! Transferred ist falsch: " << transferred << " -- setze auf: " << x << endl;
            transferred = x;
        }
    }
    else
    {
        transferred = 0;
    }

    partner = users->getUser(pt.get<string>("partner"));
    transfer_accepted = pt.get<bool>("accepted");
    transfer_paused = pt.get<bool>("paused");
    try_resume_paused_transfer = pt.get("try_resume", false);
}

FileInfo::~FileInfo()
{
}


/** getter / setter **/
string FileInfo::get_key()
{
  return key;
}

string FileInfo::get_filename()
{
  return filename;
}

uint64_t FileInfo::get_size()
{
  return size;
}

bool FileInfo::get_acceptance()
{
    return transfer_accepted;
}

TransferBundlePtr FileInfo::get_bundle() const
{
    return bundle;
}



void FileInfo::set_filename(string filename)
{
    this->filename = filename;
}

void FileInfo::set_key(string key)
{
    this->key = key;
}

void FileInfo::set_partner(UserPtr partner)
{
    this->partner = partner;
}

void FileInfo::set_acceptance(bool b)
{
    transfer_accepted = b;
    
    trigger_timing();
}

string FileInfo::get_path()
{
  return path;
}

void FileInfo::set_path(string p)
{
  path = p;
}

void FileInfo::set_size(uint64_t size)
{
    this->size = size;
}

UserPtr FileInfo::get_partner()
{
  return partner;
}

uint64_t FileInfo::get_transferred()
{
    return transferred;
}

void FileInfo::set_bundle(TransferBundlePtr bundle)
{
    this->bundle = bundle;
}


void FileInfo::set_transferred(uint64_t pos)
{
    transferred = pos;
}


void FileInfo::set_pause(bool pause, bool just_hold)
{
    transfer_paused = pause;
    try_resume_paused_transfer = just_hold;
    
    trigger_timing();
}


bool FileInfo::is_paused()
{
    return transfer_paused;
}

bool FileInfo::get_try_resume()
{
    return try_resume_paused_transfer;
}

bool FileInfo::is_complete()
{
    return size == transferred;
}

void FileInfo::set_finished()
{
    finished = true;
}

void FileInfo::inc_transferred(uint64_t n)
{
    transferred += n;
}



/** file operations **/
// appends data, returns progress in percent
// FIXME blocking....
int FileInfo::append(char* buff, int len)  // TODO, hier kann man bestimmt die eine oder andere exception schmeißen
{
    // first write -> open file
    file.exceptions(ofstream::failbit);  // XXX experimentell. exception, wenn file nicht geöffnet werden kann..
    if ( ! file.is_open() )
    {
        // create path if it doesn't exist
        boost::filesystem::path path_ = path;
        if ( path_.has_parent_path() && ! boost::filesystem::is_directory(path_.parent_path()) )
        {
            boost::filesystem::create_directories(path_.parent_path());
        }

        // * open file *
        file.open(path.c_str(), ofstream::binary | ofstream::app);
    }
    //  assert(transferred == file.tellp());

    // write
    file.write(buff, len);

    transferred += len;
    return (int) ( (transferred * 100) / size );
}



void FileInfo::status(ptree &pt)
{
    pt.add("key", key);
    pt.add("filename", filename);
    pt.add("partner", partner->get_name());
    pt.add("transferred", transferred);
    pt.add("size", size);

    string s;
    if ( finished )
        s = "finished";
    else if ( ! partner->is_connected() )
        s = "disconnected";
    else if (transfer_accepted)
    {
        if ( transfer_paused )
            s = "paused";
        else
            s = "active";
    }
    else
    {
        s = "waiting";
    }
    pt.add("state", s);
    
    pt.add("time_consumed", get_transfer_time());
}


void FileInfo::save(ptree &pt)
{
    pt.put("key", key);
    pt.put("filename", filename);
    pt.put("size", size);
    pt.put("transferred", transferred);

    pt.put("partner", partner->get_name());
    pt.put("path", path);
    pt.put("accepted", transfer_accepted);

    // always save a transfer as paused, but if it's still running, mark as hold (try_resume)
    pt.put("paused", true);
    if ( ! transfer_paused )
        try_resume_paused_transfer = true;

    pt.put("try_resume", try_resume_paused_transfer);
    
    // time consumed
    trigger_timing();
    pt.put("time_consumed", time_consumed);
}


void FileInfo::file_close()
{
    if ( file.is_open() )
    {
        file.close();
    }
}


/** timing **/
time_t FileInfo::get_transfer_time()
{
    time_t timespan = 0;
    
    if ( unpaused_at > 0 )
    {
        timespan = difftime(time(NULL), unpaused_at);
    }
    
    return time_consumed + timespan;
}

void FileInfo::inc_unacked_pkg()
{
    unacked_pkg++;
}

void FileInfo::dec_unacked_pkg()
{
    unacked_pkg--;
}

int FileInfo::get_unacked_pkg() const
{
    return unacked_pkg;
}

bool FileInfo::is_active()
{
    return transfer_accepted && ! transfer_paused && transferred < size;
}

void FileInfo::trigger_timing()
{
    // BRANCH: transfer started / resumed
    if ( transfer_accepted && ! transfer_paused )
    {
        if( unpaused_at == 0 )
        {
            unpaused_at = time(NULL);
        }
    }
    
    // BRANCH: transfer stopped
    else
    {
        if( unpaused_at != 0 )
        {
            time_consumed = get_transfer_time();
            unpaused_at = 0;
        }
    }
}
/** [timing] **/


/** operators **/
bool FileInfo::operator!(void)
{
  return key.length() == 0;
}
